
//
// Copyright 2009 Arne Halvorsen (arne.halvorsen@gmail.com)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

// Project page: http://code.google.com/p/mf4j/

//
// 091116 - aha42 - Starts in line change tracking.
// 091120 - aha42 - getPhotosInSet(String, String, String, String) and
//                  setPerPage(int).
// 091121 - aha42 - Added project page link (google code) to header comment.
// 091203a- aha42 - Removed that calling getCollectionTrees could cause exit!
// 091203b- aha42 - getPhotoSizes(String).
// 091210 - aha42 - IOException -> FlickrException.
//

package org.aha.mf4j;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.aha.mf4j.response.FlickrObject;
import org.aha.mf4j.util.CommandLineParser;
import org.aha.mf4j.visitor.PrintVisitor;
import org.aha.mf4j.visitor.Visitor;

/**
 * <p>
 *   Some potential useful calls to the API.
 * </p>
 * <p>
 *   This servers both as example code for using the API and being useful 
 *   convenient methods for some applications.
 * </p>
 * @author Arne Halvorsen (aha42)
 */
public final class FlickrCalls 
{
  private final FlickrSession m_session;
  
  private int m_perPage=0;
  
  /**
   * <p>
   *   Constructor.
   * </p>
   * @param fs Session.
   */
  public FlickrCalls(FlickrSession fs)
  {
    if (fs==null)
    {
      throw new NullPointerException("fs");
    }
    
    m_session=fs;
  }
  
  /**
   * <p>
   *   Sets how many items to fetch per page, argument for Flickr API methods 
   *   that takes such an argument.
   * </p>
   * @param v Value, if {@code 0} uses Flickr method default value.
   * @return {@code this}.
   * @throws {@code this}.
   */
  public FlickrCalls setPerPage(int v)
  {
    if (v<0)
    {
      throw new IllegalArgumentException("v<0 : "+v);
    }
    
    m_perPage=v;
    return this;
  }
  
  /**
   * <p>
   *   Gets 
   *   {@link FlickrObject}s parsed from a photo's size element types.  
   * </p>
   * <p>
   *   Uses the
   *   <a href='http://www.flickr.com/services/api/flickr.photos.getSizes.html'>
   *     flickr.photos.getSizes
   *   </a>
   *   method.
   * </p>
   * @param id   Photo's id.
   * @return Parsed result.
   * @throws FlickrException If fails.
   */
  public FlickrObject[] getPhotoSizes(String id) throws FlickrException
  {
    if (id==null)
    {
      throw new NullPointerException("id");
    }
    
    return m_session.createRequest("flickr.photos.getSizes")
      .setArg("photo_id", id).perform().getMandatoryChild("sizes")
      .getChildren("size");
  }
  
  /**
   * <p>
   *   Gets 
   *   {@link FlickrObject} representing Flickr collections. 
   * </p>
   * <p>
   *   Uses the
   *   <a href='http://www.flickr.com/services/api/flickr.collections.getTree.html'>
   *     flickr.collections.getTree
   *   </a>
   *   method.
   * </p>
   * @param cid  Collection id, may be {@code null}.
   * @param nsid User id (nsid), if {@code null} uses authenticated user.
   * @return Parsed result.
   * @throws IllegalArgumentException If {@code nsid} is {@code null} and given
   *         session is not authenticated.
   * @throws FlickrException If fails.
   */
  public FlickrObject[] getCollectionTrees(String cid, String nsid) 
    throws FlickrException
  {
    String method="flickr.collections.getTree";
    
    Request req=m_session.createRequest(method);
    
    nsid=(nsid==null) ? m_session.getNsid() : nsid;
    
    req.setArg("user_id", nsid);
    
    if (cid!=null) req.setArg("collection_id", cid);
    
    FlickrObject fo=req.perform();
    
    FlickrObject collectionsFo=fo.getMandatoryChild("collections");
    return collectionsFo.getChildren("collection");
  }
  
  /**
   * <p>
   *   Gets
   *   {@link FlickrObject}s of 
   *   {@link FlickrObject#getType() type} {@code photo} representing photos
   *   in a given photo set.
   * </p>
   * <p>
   *   Uses the
   *   <a href='http://www.flickr.com/services/api/flickr.photosets.getPhotos.html'>
   *     flickr.photosets.getPhotos
   *   </a>
   *   method.
   * </p> 
   * @param id            Id to the set.
   * @param extras        The {@code extras} parameter value to the Flickr 
   *                      method, may be {@code null}. 
   * @param media         The {@code medis} parameter value to the Flickr 
   *                      method, may be {@code null}.
   * @param privacyFilter The {@code privacy_filter} parameter value to the 
   *                      Flickr method, may be {@code null}.
   * @return Fetched.
   * @throws IllegalArgumentException If {@code media} or {@code privacy_filter}
   *         are not {@code null} but not legal values!
   * @throws FlickrException If fails.
   */
  public FlickrObject[] getPhotosInSet(String id, String extras, String media, 
    String privacyFilter) throws FlickrException
  {
    if (id==null)
    {
      throw new NullPointerException("id");
    }
    
    Request req=m_session.createRequest("flickr.photosets.getPhotos");
    
    req.setArg("photoset_id", id);
    
    if (extras!=null) req.setArg("extras", extras);
    
    if (media!=null)
    {
      FlickrMethodArguments.checkMedia(media);
      req.setArg("media", media);
    }
    
    if (privacyFilter!=null)
    {
      FlickrMethodArguments.checkPrivacyFilter(privacyFilter);
      req.setArg("privacy_filter", privacyFilter);
    }
    
    if (m_perPage>0) req.setArg("per_page", m_perPage);
    
    FlickrObject resFo=req.perform();
    
    FlickrObject photoSetFo=resFo.getMandatoryChild("photoset");
    int pages=photoSetFo.getMandatoryIntAtrribute("pages");
    
    List<FlickrObject> fetched=new ArrayList<FlickrObject>();
    
    int page=1;
    while (page<=pages)
    {      
      FlickrObject[] photoFos=photoSetFo.getChildren("photo");
      int n=photoFos.length;
      for (int i=0; i<n; i++) fetched.add(photoFos[i]);  
      page++;
      
      if (page<=pages)
      {
        req.setArg("page", page);
        resFo=req.perform();
        photoSetFo=resFo.getMandatoryChild("photoset");
      }
    }
    
    return fetched.toArray(FlickrObject.E_ARRAY);
  }
  
  /**
   * <p>
   *   Execute a request to Flickr using one of the methods found in this class
   *   and prints parsed result to 
   *   {@link System#out standard out}.
   * </p>
   * <h3>Accepted options</h3>
   * <table border='1'>
   *   <tr><th>Option</th><th>Description</th><th>Mandatory</th></tr>
   *   
   *   <tr>
   *     <td>{@code -key <api_key>}</td>
   *     <td>The Flickr API Key the application got assigned.</td>
   *     <td>Yes.</td>
   *   </tr>
   *   
   *   <tr>
   *     <td>{@code -auth <perms>}</td>
   *     <td>
   *       Says that is to perform authentication requesting the given
   *       permissions.
   *     </td>
   *     <td>
   *       Only if need authentication for method to call.
   *     </td>
   *   </tr>
   *   
   *   <tr>
   *     <td>{@code -secret <secret>}</td>
   *     <td>Shared secret between application and Flickr.</td>
   *     <td>Not needed if authentication is not needed</td>
   *   </tr>
   *   
   *   <tr>
   *     <td>{@code -method <name>}</td>
   *     <td>Name on Flickr method to call.</td>
   *     <td>
   *       No, default is to call the {@code flickr.collections.getTree}
   *       method.
   *     </td>
   *   </tr>
   *   
   *   <tr>
   *     <td>{@code -perpage <n>}</td>
   *     <td>
   *       The value passes as per_page argument to Flickr API methods that
   *       accept such argument. If {@code 0} uses method's default.
   *     </td>
   *     <td>
   *       No, if not given uses method's default value.
   *     </td>
   *   </tr>   
   *   
   * </table>
   * 
   * <p>
   *   In addition to the above options the ones below are used by the
   *   specific method calls.
   * </p>
   * 
   * <h3>Supported methods</h3>
   * 
   * <h4>
   *   <a href='http://www.flickr.com/services/api/flickr.collections.getTree.html'>
   *     flickr.collections.getTree
   *   </a>
   * </h4> 
   * 
   * Does require authentication ({@code -auth <perms>} option) with 
   * {@code read} permission.
   * 
   * <table border='1'>
   *   <tr>
   *     <th>Option</th><th>Description</th><th>Mandatory</th>
   *   </tr>
   *   
   *   <tr>
   *     <td>{@code -auth <read>}</td>
   *     <td>
   *       Must authenticate with at least {@code read} permissions.
   *     </td>
   *     <td>
   *       Yes.
   *     </td>
   *   </tr>   
   *   
   *   <tr>
   *     <td>{@code -nsid <nsid>}</td>
   *     <td>User id to fetch collection tree for.</td>
   *     <td>No.</td>
   *   </tr>
   *   
   *   <tr>
   *     <td>{@code -cid <id>}</td>
   *     <td>Id of collection to fetch.</td>
   *     <td>No.</td>
   *   </tr>
   * </table>
   * 
   * <h4>
   *   <a href='http://www.flickr.com/services/api/flickr.photos.getSizes.html'>
   *     flickr.photos.getSizes
   *   </a>
   * </h4> 
   * 
   * <table border='1'>
   *   <tr>
   *     <th>Option</th><th>Description</th><th>Mandatory</th>
   *   </tr>
   *   
   *   <tr>
   *     <td>{@code -pid <id>}</td>
   *     <td>
   *       Id of photo to get sizes for.
   *     </td>
   *     <td>
   *       Yes.
   *     </td>
   *   </tr>   
   *   
   * </table> 
   * 
   * <h4>
   *   <a href='http://www.flickr.com/services/api/flickr.photosets.getPhotos.html'>
   *     flickr.photosets.getPhotos
   *   </a>
   * </h4>
   * 
   * <table border='1'>
   *
   *   <tr>
   *     <th>Option</th><th>Description</th><th>Mandatory</th>
   *   </tr>   
   *   
   *   <tr>
   *     <td>{@code -sid <sid>}</td>
   *     <td>Photo set id</td>
   *     <td>Yes.</td>
   *   </tr>
   *   
   *   <tr>
   *     <td>{@code -extras <value>}</td>
   *     <td>Value for the {@code extras} parameter to method.</td>
   *     <td>No.</td>
   *   </tr>
   *   
   *   <tr>
   *     <td>{@code -media <value>}</td>
   *     <td>Value for the {@code media} parameter to method.</td>
   *     <td>No.</td>
   *   </tr>
   *   
   *   <tr>
   *     <td>{@code -privacy_filter <value>}</td>
   *     <td>Value for the {@code privacy_filter} parameter to method.</td>
   *     <td>No.</td>
   *   </tr>   
   *   
   * </table> 
   * 
   * <h3>Termination</h3>
   * <p>
   *   If all ok terminates with return code {@code 0}.
   * </p>
   * <h4>Errors</h4>
   * <table border='1'>
   *   <tr><th>Code</th><th>Description</th>
   *   
   *   <tr>
   *     <td>{@code -1}</td>
   *     <td>
   *       Authentication requested by the {@code -auth} option but no secret
   *       given by the {@code -secret} option.
   *     </td> 
   *   </tr>
   *   
   *   <tr>
   *     <td>{@code -2}</td>
   *     <td>
   *       Permission specified by the option {@code -auth} is not valid Flickr
   *       permissions.
   *     </td> 
   *   </tr>
   *   
   *   <tr>
   *     <td>{@code -3}</td>
   *     <td>
   *       The name specified by the {@code -methode} options does not name a
   *       Flickr method.
   *     </td> 
   *   </tr>   
   *   
   *   <tr>
   *     <td>{@code -4}</td>
   *     <td>
   *       The name specified by the {@code -methode} options does name a
   *       Flickr method but this class does not implement a call to it.
   *     </td> 
   *   </tr>
   *   
   *   <tr>
   *     <td>{@code -20}</td>
   *     <td>
   *       An option value could not be parsed, for example the 
   *       {@code -perpage} value is not an integer. 
   *     </td> 
   *   </tr>
   *   
   *   <tr>
   *     <td>{@code -25}</td>
   *     <td>
   *       An option value could not legal, for example the 
   *       {@code -perpage} value is {@code <0}. 
   *     </td> 
   *   </tr>         
   *   
   *   <tr>
   *     <td>{@code -99}</td>
   *     <td>
   *       Unknown fatal error occurred, should never happen.
   *     </td> 
   *   </tr>   
   *   
   *   <tr>
   *     <td>{@code -100}</td>
   *     <td>
   *       An 
   *       {@link IOException} happened when tried to execute method request.
   *     </td> 
   *   </tr>   
   *      
   * </table>
   * @param args Command line, see above tables for accepted options.
   */
  public static void main(String[] args) 
  {
    try
    {
      CommandLineParser clp=new CommandLineParser(args, -20);
      
      String apiKey=clp.mandatoryValue("-key");      
      String perms=clp.value("-auth");
      String secret=clp.value("-secret");
      String method=clp.value("-method", "flickr.collections.getTree");
      
      FlickrSession fs=(secret==null) ? new FlickrSession(apiKey) 
                                      : new FlickrSession(apiKey, secret);
      
      int perPage=clp.intValue("-perpage", 0);
      if (perPage<0)
      {
        System.err.println("-perpage <n> : n<0 : "+perPage);
        System.exit(-25);
      }
      
      if (perms!=null) 
      {
        if (secret==null)
        {
          System.err.println("Authentication requested but no secret given.");
          System.exit(-1);
        }
        try
        {
          fs.authenticate(perms);
        }
        catch (IllegalArgumentException iax)
        {
          System.err.println("Not valid permission.");
          System.exit(-2);
        }
      }
      
      FlickrCalls fc=new FlickrCalls(fs);
      
      FlickrObject parsedResponse=null;
      FlickrObject[] parsedResponseA=null;
      
      //
      // flickr.collections.getTree
      //
      
      if (method.equals("flickr.collections.getTree"))
      {
        String nsid=clp.value("-nsid");
        String cid=clp.value("-cid");
        parsedResponseA=fc.getCollectionTrees(cid, nsid);
      }
      
      else if (method.equals("flickr.photos.getSizes"))
      {
        String pid=clp.value("-pid");
        parsedResponseA=fc.getPhotoSizes(pid);
      }
      
      //
      // flickr.photosets.getPhotos
      //
      
      else if (method.equals("flickr.photosets.getPhotos"))
      {
        String sid=clp.mandatoryValue("-sid");
        String extras=clp.value("-extras");
        String media=clp.value("-media");
        String privFilter=clp.value("-privacy_filter");
        parsedResponseA=fc.getPhotosInSet(sid, extras, media, privFilter);
      }
      else 
      {
        if (!fs.getReflection().methodExists(method))
        {
          System.err.println("Not a flickr method : "+method);
          System.exit(-3);
        }
        else
        {
          System.err.println("Not supported method : "+method);
          System.exit(-4);
        }        
      }
      
      Visitor fov=PrintVisitor.INSTANCE;
           if (parsedResponse!=null)  parsedResponse.visit(fov);
      else if (parsedResponseA!=null) FlickrObject.visit(parsedResponseA, fov);
      else
      {
        System.err.println("Strange, no exceptions but no parsed result!");
        System.exit(-99);
      }
      
      System.exit(0);
    }
    catch (IllegalArgumentException iax)
    {
      System.err.println(iax.getMessage());
      System.exit(-25);
    }
    catch (IOException iox)
    {
      iox.printStackTrace();
      System.exit(-100);
    }
  }
  
}
